#include "logicchipbaseentity.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <QJsonParseError>
#include <QDebug>
#include <QPainter>
#include <QColor>
#include <QStyleOptionGraphicsItem>
#include <QPainterPath>
#include "../../aurasim.h"

LogicChipBaseEntity::LogicChipBaseEntity(QGraphicsItem *parent) :
    Entity (parent),
    mPinCount(0),
    mVcc(-1),
    mGnd(-1),
    mW(0),
    mH(0)
{
    setName("LogicChipBase");
}

void LogicChipBaseEntity::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(widget);
    QColor bgColor(60 ,60 ,60);
    if(option->state & QStyle::State_MouseOver || option->state & QStyle::State_Selected) {
        bgColor = bgColor.lighter(120);
    }
    painter->fillRect(boundingRect(), bgColor);

    QPainterPath path;
    path.addRoundedRect(QRectF(-8, -mH / 2.0 + 7, 16, 16), 8, 8);
    painter->fillPath(path, bgColor.lighter(150));
}

QRectF LogicChipBaseEntity::boundingRect() const
{
    return QRectF(-mW / 2.0, -mH / 2.0, mW, mH);
}

void LogicChipBaseEntity::initChipDatas()
{
    if (!QFile::exists(mDataFile)) {
        qDebug() << "no valid chip data file named " << mDataFile;
        return;
    }

    // read data

    QFile f(mDataFile);
    if (!f.open(QIODevice::ReadOnly)){
        qDebug() << f.errorString();
        f.close();
        return;
    }

    QByteArray data = f.readAll();
    f.close();

    QJsonParseError* jspError = nullptr;
    QJsonDocument doc = QJsonDocument::fromJson(data, jspError);
    if (jspError) {
        qDebug() << jspError->errorString();
        return;
    }

    QJsonObject obj = doc.object();
    mPinCount = obj["pinCount"].toInt();
    mVcc = obj["vcc"].toInt() - 1;
    mGnd = obj["gnd"].toInt() - 1;
    mDataSheetUrl = obj["dataSheet"].toString();
    const QJsonArray& inputsJsArray = obj["inputs"].toArray();
    foreach (const QJsonValue& val, inputsJsArray) {
        mInputs << val.toInt() - 1;
    }

    const QJsonArray& outputsJsArray = obj["outputs"].toArray();
    foreach (const QJsonValue& val, outputsJsArray) {
        mOutputs << val.toInt() - 1;
    }

    const QJsonArray& truthTableJsArray = obj["truthTable"].toArray();
    foreach(const QJsonValue& val, truthTableJsArray) {
        const QJsonObject& oTable = val.toObject();
        mTruthTable.insert(oTable["in"].toString(), oTable["out"].toString().split(","));
    }

    mW = 90;
    mH = 20 + (mPinCount / 2) * 20;

    // create pins
    for (int i = 0; i < mPinCount; i++) {
        QSharedPointer<PinEntity> pin = QSharedPointer<PinEntity>(new PinEntity(this));
        Aurasim::entityManager()->addSubEntity(pin.data());
        mPins.append(pin);
        qreal x = i < mPinCount / 2 ? -mW / 2.0 + 8 : mW / 2.0 - 8;
        qreal y = i < mPinCount / 2 ? -mH / 2.0 + (mH - (mPinCount / 2 - 1) * 20) / 2 + i * 20 : mH / 2.0 - (mH - (mPinCount / 2 - 1) * 20) / 2 - (i - (mPinCount / 2)) * 20;
        pin->setPos(x, y);
        pin->setPinDirection(i < mPinCount / 2 ? PinDirection::Left : PinDirection::Right);
    }

    foreach (const int& out, mOutputs) {
        mPins[out]->setPinMode(1);
    }

    foreach (const int& in, mInputs) {
        mPins[in]->addCurrentNode(mPins[mGnd]->id());
    }

    if(mVcc >= 0 && mGnd >= 0) {
        mPins[mVcc]->addCurrentNode(mPins[mGnd]->id());
    }
    prepareGeometryChange();

}

void LogicChipBaseEntity::tick(const int &ms)
{
    Q_UNUSED(ms)

    // if this chip has no valid current, all outputs should turn to 0 voltages.
    if (!mPins[mGnd]->hasCrrent()) {
        foreach (const int& out, mOutputs)
            mPins[out]->setVolt(0);
        return;
    }

    QString inFlags = "";
    foreach ( const int& in, mInputs) {
        const qreal& volt = mPins[in]->volt();
        QString inFlag = volt > 1 ? "1" : "0";
        inFlags += inFlag;
    }

    // if inputflags has no '*' flags, truth map has theirs map output flags.
    if (mTruthTable.contains(inFlags)) {
        int idx = 0;
        foreach (const QString& outFlag, mTruthTable.value(inFlags)) {
            qreal volt = outFlag == "1" ? 3.3 : 0;
            mPins[mOutputs[idx]]->setVolt(volt);
            idx++;
        }
    }
    // if inputflags contains '*', find a first match flags output flags on the truth map.
    else{
        foreach (const QString& key, mTruthTable.keys()) {
            bool match = true;
            for (int idx = 0; idx < key.length(); idx++) {
                const QChar flag = key.at(idx);
                if (flag == '*')
                    continue;
                if (flag != inFlags.at(idx)) {
                    match = false;
                    break;
                }
            }
            if (match) {
                int idx = 0;
                foreach( const QString& outFlag, mTruthTable.value(key)) {
                    qreal volt = outFlag == "1" ? 3.3 : 0;
                    mPins[mOutputs[idx]]->setVolt(volt);
                    idx++;
                }
                break;
            }

            // TODO (tommego:2019-1-10) disable this code block, which cannot handle repeat role for in out pins caclulation.
            // ( like this ocject : {"in": "*1-**", "out": "*1"} )
//            else{
//                const int& idxRung = key.indexOf("-");
//                int inSize = key.length() - idxRung - 1;
//                for(int i = 0; i < mOutputs.length(); i++) {
//                    QString newFlags = inFlags.left(idxRung);
//                    for(int j = 0; j < inSize; j++) {
//                        newFlags += inFlags.at(i+inSize + j);
//                    }
//                    QString newKey = key;
//                    newKey = newKey.replace("-", "");

//                    if(newKey == newFlags) {
//                        QStringList outputDatas = mTruthTable.value(key);
//                        for(int idx = 0; idx < outputDatas.length(); idx++) {
//                            QString strOut = outputDatas.at(idx);
//                            qreal volt = strOut == "1" ? 3.3 : 0;
//                            mPins[mOutputs[i * inSize + idx]]->setVolt(volt);
//                        }
//                    } else{
//                        bool match = true;
//                        qDebug() << newKey << newFlags;
//                        for(int idx = 0; idx < newKey.length(); idx++) {
//                            const QChar& flag = newKey.at(idx);
//                            if (flag == '*')
//                                continue;
//                            if (flag != newFlags.at(idx)) {
//                                match = false;
//                                break;
//                            }
//                        }

//                        if (match) {
//                            QStringList outputDatas = mTruthTable.value(key);
//                            for(int idx = 0; idx < outputDatas.length(); idx++) {
//                                QString strOut = outputDatas.at(idx);
//                                qreal volt = strOut == "1" ? 3.3 : 0;
//                                mPins[mOutputs[i]]->setVolt(volt);
//                            }
//                        }
//                    }
//                }
//            }
        }
    }
    // update pin status
}
